Raphael.fn.connection = function(obj1, obj2, line, bg) {
	if (obj1.line && obj1.from && obj1.to) {
		line = obj1;
		obj1 = line.from;
		obj2 = line.to;
	}
	var bb1 = obj1.getBBox();
	var bb2;
	if (obj2 == null) {
		bb2 = {
			x : 0,
			y : 0,
			height : 0,
			width : 0
		};
	} else {
		bb2 = obj2.getBBox();
	}

	var p = [ {
		x : bb1.x + bb1.width / 2,
		y : bb1.y - 1
	},

	{
		x : bb1.x + bb1.width / 2,
		y : bb1.y + bb1.height + 1
	},

	{
		x : bb1.x - 1,
		y : bb1.y + bb1.height / 2
	},

	{
		x : bb1.x + bb1.width + 1,
		y : bb1.y + bb1.height / 2
	},

	{
		x : bb2.x + bb2.width / 2,
		y : bb2.y - 1
	},

	{
		x : bb2.x + bb2.width / 2,
		y : bb2.y + bb2.height + 1
	},

	{
		x : bb2.x - 1,
		y : bb2.y + bb2.height / 2
	},

	{
		x : bb2.x + bb2.width + 1,
		y : bb2.y + bb2.height / 2
	} ], d = {}, dis = [];
	for ( var i = 0; i < 4; i++) {
		for ( var j = 4; j < 8; j++) {
			var dx = Math.abs(p[i].x - p[j].x), dy = Math.abs(p[i].y - p[j].y);
			if ((i == j - 4)
					|| (((i != 3 && j != 6) || p[i].x < p[j].x)
							&& ((i != 2 && j != 7) || p[i].x > p[j].x)
							&& ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) {
				dis.push(dx + dy);
				d[dis[dis.length - 1]] = [ i, j ];
			}
		}
	}
	if (dis.length == 0) {
		var res = [ 0, 4 ];
	} else {
		res = d[Math.min.apply(Math, dis)];
	}
	var x1 = p[res[0]].x, y1 = p[res[0]].y, x4 = p[res[1]].x, y4 = p[res[1]].y;
	dx = Math.max(Math.abs(x1 - x4) / 2, 10);
	dy = Math.max(Math.abs(y1 - y4) / 2, 10);
	var x2 = [ x1, x1, x1 - dx, x1 + dx ][res[0]].toFixed(3), y2 = [ y1 - dy,
			y1 + dy, y1, y1 ][res[0]].toFixed(3), x3 = [ 0, 0, 0, 0, x4, x4,
			x4 - dx, x4 + dx ][res[1]].toFixed(3), y3 = [ 0, 0, 0, 0, y1 + dy,
			y1 - dy, y4, y4 ][res[1]].toFixed(3);
	var path = [ "M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3,
			x4.toFixed(3), y4.toFixed(3) ].join(",");

	if (line && line.line) {
		line.bg && line.bg.attr({
			path : path
		});
		line.line.attr({
			path : path
		});
		var pt = line.line.getPointAtLength(line.line.getTotalLength() - 5);
		// test4 = r.arrow(pt.x, pt.y, x4, y4, 5);
		var angle = Math.atan2(pt.x - x4, y4 - pt.y);
		angle = (angle / (2 * Math.PI)) * 360;
		line.arrow.remove();
		line.arrow = this.path(
				"M" + x4 + " " + y4 + " L" + (x4 - 5) + " " + (y4 - 5) + " L"
						+ (x4 - 5) + " " + (y4 + 5) + " L" + x4 + " " + y4)
				.attr("fill", "black").rotate((90 + angle), x4, y4);
		return line;
	} else {
		var color = typeof line == "string" ? line : "#000";

		var w = this.path(path).attr({
			stroke : color,
			fill : "none"
		});

		var pt2 = w.getPointAtLength(w.getTotalLength() - 5);
		// test4 = r.arrow(pt.x, pt.y, x4, y4, 5);
		var angle2 = Math.atan2(pt2.x - x4, y4 - pt2.y);
		angle = (angle2 / (2 * Math.PI)) * 360;
		var ar = this.path(
				"M" + x4 + " " + y4 + " L" + (x4 - 5) + " " + (y4 - 5) + " L"
						+ (x4 - 5) + " " + (y4 + 5) + " L" + x4 + " " + y4)
				.attr("fill", "black").rotate((90 + angle), x4, y4);

		return {
			bg : bg && bg.split && this.path(path).attr({
				stroke : bg.split("|")[0],
				fill : "none",
				"stroke-width" : bg.split("|")[1] || 3
			}),
			line : w,
			from : obj1,
			to : obj2,
			arrow : ar
		};
	}
};

var vars = {};

function Start(x, y) {
	var text = r.text(x, y, "Start");
	var shape = r.ellipse(x, y, 20, 20);
	shape.attr({
		fill : '#ffffff',
		stroke : 'gray',
		"fill-opacity" : 0,
		"stroke-width" : 2
	});
	shape.drag(move, dragStart, dragEnd);
	shape.click(click);
	shape._text = text;
	shape._connection = null;
	shape._rconnections = [];
	shape._interpret = function() {
		vars = {};
		console.log("start");
		if (shape._connection) {
			shape._connection.to._interpret();
		}
	};
	shape._type = "start";
	this.shape = shape;
}

function End(x, y) {
	var text = r.text(x, y, "End");
	var shape = r.ellipse(x, y, 20, 20);
	shape.attr({
		fill : '#ffffff',
		stroke : 'gray',
		"fill-opacity" : 0,
		"stroke-width" : 2
	});
	shape.drag(move, dragStart, dragEnd);
	shape.click(click);
	shape._text = text;
	shape._connection = null;
	shape._rconnections = [];
	shape._type = "end";
	shape._interpret = function() {
		console.log("end");
	};
	shape._type = "end";
	this.shape = shape;
}

function Input(x, y) {
	var varName = prompt("Store into variable:");
	var shape = r.rect(x, y, 100, 40, 5);
	var text = r.text(x + (shape.attr('width') / 2), y
			+ (shape.attr('height') / 2), "input into " + varName);
	shape.attr({
		fill : '#ffffff',
		stroke : 'gray',
		"fill-opacity" : 0,
		"stroke-width" : 2
	});
	shape.drag(move, dragStart, dragEnd);
	shape.click(click);
	shape.dblclick(makeDblClick(shape, text));
	shape._text = text;
	shape._connection = null;
	shape._rconnections = [];
	shape._interpret = function() {
		var value = eval(prompt(varName + ":"));
		vars[varName] = value;
		if (shape._connection) {
			shape._connection.to._interpret();
		}
	};
	shape._type = "input";
	this.shape = shape;
}

function OAlert(x, y) {
	var c = prompt("Expression:");
	var shape = r.rect(x, y, 100, 40, 5);
	var text = r.text(x + (shape.attr('width') / 2), y
			+ (shape.attr('height') / 2), "alert " + c);
	shape.attr({
		fill : '#ffffff',
		stroke : 'gray',
		"fill-opacity" : 0,
		"stroke-width" : 2
	});
	shape.drag(move, dragStart, dragEnd);
	shape.click(click);
	shape.dblclick(makeDblClick(shape, text));
	shape._text = text;
	shape._connection = null;
	shape._rconnections = [];
	shape._interpret = function() {
		alert(eval(c));
		if (shape._connection) {
			shape._connection.to._interpret();
		}
	};
	shape._type = "alert";
	this.shape = shape;
}

function OPrint(x, y) {
	var c = prompt("Expression");
	var shape = r.rect(x, y, 100, 40, 5);
	var text = r.text(x + (shape.attr('width') / 2), y
			+ (shape.attr('height') / 2), "print " + c);
	shape.attr({
		fill : '#ffffff',
		stroke : 'gray',
		"fill-opacity" : 0,
		"stroke-width" : 2
	});
	shape.drag(move, dragStart, dragEnd);
	shape.click(click);
	shape.dblclick(makeDblClick(shape, text));
	shape._text = text;
	shape._connection = null;
	shape._rconnections = [];
	shape._interpret = function() {
		console.log(eval(c));
		if (shape._connection) {
			shape._connection.to._interpret();
		}
	};
	shape._type = "print";
	this.shape = shape;
}

function VariableDecl(x, y) {
	var n = prompt("Variable name:");
	var v = eval(prompt("Initial value:"));
	var shape = r.rect(x, y, 100, 40, 5);
	var text = r.text(x + (shape.attr('width') / 2), y
			+ (shape.attr('height') / 2), "var " + n + " = " + v);
	shape.attr({
		fill : '#ffffff',
		stroke : 'gray',
		"fill-opacity" : 0,
		"stroke-width" : 2
	});
	shape.drag(move, dragStart, dragEnd);
	shape.click(click);
	shape.dblclick(makeDblClick(shape, text));
	shape._text = text;
	shape._connection = null;
	shape._rconnections = [];
	shape._interpret = function() {
		vars[n] = v;
		if (shape._connection) {
			shape._connection.to._interpret();
		}
	};
	shape._type = "vardecl";
	this.shape = shape;
}

function Assignment(x, y) {
	var n = prompt("Variable name:");
	var exp = prompt("Expression:");
	var shape = r.rect(x, y, 100, 40, 5);
	var text = r.text(x + (shape.attr('width') / 2), y
			+ (shape.attr('height') / 2), n + " = " + exp);
	shape.attr({
		fill : '#ffffff',
		stroke : 'gray',
		"fill-opacity" : 0,
		"stroke-width" : 2
	});
	shape.drag(move, dragStart, dragEnd);
	shape.click(click);
	shape.dblclick(makeDblClick(shape, text));
	shape._text = text;
	shape._connection = null;
	shape._rconnections = [];
	shape._interpret = function() {
		vars[n] = eval(exp);
		if (shape._connection) {
			shape._connection.to._interpret();
		}
	};
	shape._type = "assign";
	this.shape = shape;
}

function Condition(x, y) {
	var exp = prompt("Condition:");
	var shape = r.rect(x, y, 100, 40, 5);
	var text = r.text(x + (shape.attr('width') / 2), y
			+ (shape.attr('height') / 2), "if " + exp);
	shape.attr({
		fill : '#ffffff',
		stroke : 'gray',
		"fill-opacity" : 0,
		"stroke-width" : 2
	});
	shape.drag(move, dragStart, dragEnd);
	shape.click(click);
	shape.dblclick(makeDblClick(shape, text));
	shape._text = text;
	shape._connection = -1;
	shape._connection0 = null;
	shape._connection1 = null;
	shape._rconnections = [];
	shape._interpret = function() {
		if (eval(exp) == true) {
			if (shape._connection0) {
				shape._connection0.to._interpret();
			}
		} else {
			if (shape._connection1) {
				shape._connection1.to._interpret();
			}
		}
	};
	shape._type = "cond";
	this.shape = shape;
}

var current = null;
var DEFAULT = 0;
var CONNECTING = 1;
var mode = DEFAULT;
var r;
var start;

var dragStart = function() {
	this.ox = this.type == "rect" ? this.attr("x") : this.attr("cx");
	this.oy = this.type == "rect" ? this.attr("y") : this.attr("cy");
	this.attr({
		cursor : 'pointer'
	});
	this.animate({
		"fill-opacity" : .2
	}, 500);

};

var move = function(dx, dy) {
	var att = this.type == "rect" ? {
		x : this.ox + dx,
		y : this.oy + dy
	} : {
		cx : this.ox + dx,
		cy : this.oy + dy
	};
	this.attr(att);

	if (this._text) {
		var att2 = this.type == "rect" ? {
			x : this.ox + (this.attr('width') / 2) + dx,
			y : this.oy + (this.attr('height') / 2) + dy
		} : {
			x : this.ox + dx,
			y : this.oy + dy
		};
		this._text.attr(att2);
	}
	if (this._connection === -1) {
		if (this._connection0) {
			r.connection(this._connection0);
		}
		if (this._connection1) {
			r.connection(this._connection1);
		}
	} else {
		if (this._connection) {
			r.connection(this._connection);
		}
	}
	if (this._rconnections) {
		var rcons = this._rconnections;
		for ( var i = 0; i < rcons.length; i++) {
			r.connection(rcons[i]);
		}
	}
	r.safari();
};

var dragEnd = function() {
	this.attr({
		cursor : 'default'
	});
	this.animate({
		"fill-opacity" : 0
	}, 500);
};

var click = function(evt) {
	if (evt.ctrlKey) {
		if (mode == DEFAULT) {
			if (this._type && this._type == 'end') {
				mode = DEFAULT;
			} else {
				current = this;
				mode = CONNECTING;
			}

		} else {
			if (mode == CONNECTING) {
				if (this !== current) {
					if (current._connection === -1) {
						if (current._connection0) {
							current._connection0.to = this;
							r.connection(current._connection0);
						} else {
							current._connection0 = r.connection(current, this,
									"green");
						}
						this._rconnections.push(current._connection0);
					} else {
						if (current._connection) {
							current._connection.to = this;
							r.connection(current._connection);
						} else {
							current._connection = r.connection(current, this,
									"#000");
						}
						this._rconnections.push(current._connection);
					}
					current = null;
					mode = DEFAULT;
				}
			}
		}
	} else if (evt.altKey) {
		if (mode == DEFAULT) {
			current = this;
			mode = CONNECTING;
		} else {
			if (mode == CONNECTING) {
				if (this !== current) {
					if (current._connection === -1) {
						if (current._connection1) {
							current._connection1.to = this;
							r.connection(current._connection1);
						} else {
							current._connection1 = r.connection(current, this,
									"red");
						}
					}
					this._rconnections.push(current._connection1);
					current = null;
					mode = DEFAULT;
				}
			}
		}
	} else {
		mode = DEFAULT;
	}
};

var makeDblClick = function(shape, text) {
	return function() {
		if (shape._connection == -1) {
			if (shape._connection0) {
				shape._connection0.line.remove();
				shape._connection0.arrow.remove();
			}
			if (shape._connection1) {
				shape._connection1.arrow.remove();
				shape._connection1.line.remove();
			}
		} else {
			if (shape._connection) {
				shape._connection.line.remove();
				shape._connection.arrow.remove();
			}
		}
		shape.remove();
		text.remove();
	};
}

$(document).ready(function() {
	r = Raphael("holder", 640, 480);

	$('#create-vardecl').click(function(evt) {
		new VariableDecl(150, 150);
	});

	$('#create-assign').click(function(evt) {
		new Assignment(260, 260);
	});

	$('#create-input').click(function(evt) {
		new Input(240, 240);
	});

	$('#create-print').click(function(evt) {
		new OPrint(220, 220);
	});

	$('#create-alert').click(function(evt) {
		new OAlert(220, 220);
	});

	$('#create-condition').click(function(evt) {
		new Condition(150, 150);
	});

	$('#interpret').click(function(evt) {
		execute(start);
	});

	$('#json').click(function(evt) {
		json(start);
	});

	start = new Start(30, 30);
	new End(600, 450);
});

function execute(start) {
	start.shape._interpret();
}

function jsonize(node, jsonObj) {
	console.log(node);
	jsonObj.type = node._type;
	if (node && node._connection) {
		if (node._connection == -1) {
			if (node._connection0) {
				jsonObj.child0 = {};
				jsonize(node._connection0.to, jsonObj.child0);
			}
			if (node._connection1) {
				jsonObj.child1 = {};
				jsonize(node._connection1.to, jsonObj.child1);
			}
		} else {
			jsonObj.child = {};
			jsonize(node._connection.to, jsonObj.child);
		}
	}
}

var jsonObj = {};

function json(start) {
	jsonize(start.shape, jsonObj);
	alert(JSON.stringify(jsonObj));
}